/*
 *  linux/fs/inode.c
 *
 *  (C) 1991  Linus Torvalds
 */

#include <string.h> 
#include <sys/stat.h>

#include <linux/sched.h>
#include <linux/kernel.h>
#include <linux/mm.h>
#include <asm/system.h>

// 内存i节点表（32项的数组）
struct m_inode inode_table[NR_INODE]={{0,},};

static void read_inode(struct m_inode * inode);
static void write_inode(struct m_inode * inode);

// 睡眠等待指定的i节点可用(解锁)
static inline void wait_on_inode(struct m_inode * inode)
{
	cli();
	while (inode->i_lock)
		sleep_on(&inode->i_wait);  // 之所以放在while循环中，是因为等睡眠唤醒后，再次检查是否上锁
	sti();
}

// 对inode上锁
static inline void lock_inode(struct m_inode * inode)
{
	cli();
	while (inode->i_lock)
		sleep_on(&inode->i_wait);
	inode->i_lock=1;
	sti();
}

// 解锁inode，并唤醒等待进程
static inline void unlock_inode(struct m_inode * inode)
{
	inode->i_lock=0;
	wake_up(&inode->i_wait);
}

// 移除dev设备在内存inode表中的相关inodes
void invalidate_inodes(int dev)
{
	int i;
	struct m_inode * inode;

	// 扫描内存中的i节点表数组，如果是指定设备使用的i节点就释放之。
	inode = 0+inode_table;
	for(i=0 ; i<NR_INODE ; i++,inode++) {
		wait_on_inode(inode);
		if (inode->i_dev == dev) {
			if (inode->i_count)
				printk("inode in use on removed disk\n\r");
			// 释放i节点(置设备号为0）
			inode->i_dev = inode->i_dirt = 0;
		}
	}
}

// 同步所有内存i节点到设备i节点
// 把内存i节点表中所有i节点与设备上i节点作同步操作：扫描内存中的i节点表数组，进行脏数据写入缓冲区
void sync_inodes(void)
{
	int i;
	struct m_inode * inode;

	inode = 0+inode_table;
	for(i=0 ; i<NR_INODE ; i++,inode++) {
		wait_on_inode(inode);
		// 若i节点已修改且不是管道节点，则写入缓冲区中。
		if (inode->i_dirt && !inode->i_pipe)
			write_inode(inode);
	}
}

// 文件数据块映射到盘块的处理操作。 （block 位图处理函数，bmap 一 block map）
// 参数：inode - 文件的i节点指针；block - 文件中的数据块号；create - 创建块标志。
// 具体实现：
// 在设备中找一个未使用的新磁盘块，将块号设置到inode的逻辑块字段i_zone中，并返回逻辑块号。
// 该函数把指定的文件数据块 block 对应到设备上逻辑块上，并返回逻辑块号。
// 如果创建标志create置位，则在设备上对应逻辑块不存在时就申请新磁盘块，返回文件数据块 block 对应在设备上的逻辑块号（盘块号）。
static int _bmap(struct m_inode * inode,int block,int create)
{
	struct buffer_head * bh;
	int i;

	if (block<0)
		panic("_bmap: block<0");
	if (block >= 7+512+512*512)  // 一个文件支持的最大盘块数
		panic("_bmap: block>big");
	// 块号为直接块
	if (block<7) {
		if (create && !inode->i_zone[block])
			if ((inode->i_zone[block]=new_block(inode->i_dev))) {
				inode->i_ctime=CURRENT_TIME;
				inode->i_dirt=1;
			}
		return inode->i_zone[block];
	}
	// 块号位于一次间接块中
	block -= 7;
	if (block<512) {
		// 分配一次间接块（如果未分配）
		if (create && !inode->i_zone[7])
			if ((inode->i_zone[7]=new_block(inode->i_dev))) {
				inode->i_dirt=1;
				inode->i_ctime=CURRENT_TIME;
			}
		// 如果上面的分配未获取到逻辑块
		if (!inode->i_zone[7])
			return 0;
		// 从设备上读取逻辑块数据到缓冲区块
		if (!(bh = bread(inode->i_dev,inode->i_zone[7])))
			return 0;
		// 一次间接块中的条目（块号）
		i = ((unsigned short *) (bh->b_data))[block];
		if (create && !i)
			// 分配一个新块，将块号写入一次间接块中
			if ((i=new_block(inode->i_dev))) {
				((unsigned short *) (bh->b_data))[block]=i;
				bh->b_dirt=1;
			}
		brelse(bh);
		return i;
	}
	// 块号需要保存在二次间接块中
	block -= 512;
	if (create && !inode->i_zone[8])
		if ((inode->i_zone[8]=new_block(inode->i_dev))) {
			inode->i_dirt=1;
			inode->i_ctime=CURRENT_TIME;
		}
	if (!inode->i_zone[8])
		return 0;
	if (!(bh=bread(inode->i_dev,inode->i_zone[8])))
		return 0;
	// 二级间接块在一级间接块中的块号（block>>9是二级间接块在一级间接块中的偏移值。间接块相当于一个short[512]类型的数组）
	i = ((unsigned short *)bh->b_data)[block>>9];  // 1个一级间接块存储512个二级间接块
	if (create && !i)
		if ((i=new_block(inode->i_dev))) {
			((unsigned short *) (bh->b_data))[block>>9]=i;
			bh->b_dirt=1;
		}
	brelse(bh);
	if (!i)
		return 0;
	// 读取二级间接块
	if (!(bh=bread(inode->i_dev,i)))
		return 0;
	// 数据块（由二级间接块指向）
	i = ((unsigned short *)bh->b_data)[block&511];
	if (create && !i)
		if ((i=new_block(inode->i_dev))) {
			((unsigned short *) (bh->b_data))[block&511]=i;
			bh->b_dirt=1;
		}
	brelse(bh);
	return i;
}

// 取文件数据块 block 在设备上对应的逻辑块号
// 若操作成功则返回对应的逻辑块号，否则返回0.
int bmap(struct m_inode * inode,int block)
{
	return _bmap(inode,block,0);
}

// 取文件数据块 block 在设备上对应的逻辑块号，如果不存在则创建。
// 若操作成功则返回对应的逻辑块号，否则返回0.
int create_block(struct m_inode * inode, int block)
{
	return _bmap(inode,block,1);
}

// 释放或放回 一个i节点（回写入设备）。
// 实现：
// 1、把i节点引用计数值递减1，并且：
// 		若是管道i节点，则唤醒等待的进程。退出。
// 		若是块设备文件i节点，则刷新设备。
// 2、若i节点的链接计数为0，则释放该i节点占用的所有磁盘逻辑块，并释放该i节点。		
void iput(struct m_inode * inode)
{
	if (!inode)
		return;
	wait_on_inode(inode);
	if (!inode->i_count)
		panic("iput: trying to free free inode");
	// 管道文件
	if (inode->i_pipe) {
		wake_up(&inode->i_wait);
		// 如果引用计数减1后，还有引用则返回，
		if (--inode->i_count)
			return;
		// 否则，释放管道占用的内存页面
		free_page(inode->i_size);
		inode->i_count=0;
		inode->i_dirt=0;
		inode->i_pipe=0;
		return;
	}
	// (非管道文件)如果文件未使用设备
	if (!inode->i_dev) {
		inode->i_count--;
		return;
	}
	// 其他类型文件
	// 块设备文件
	if (S_ISBLK(inode->i_mode)) {
		sync_dev(inode->i_zone[0]);  // 块文件的设备号存储在i_zone[0]中
		wait_on_inode(inode);
	}
repeat:
	if (inode->i_count>1) {
		inode->i_count--;
		return;
	}
	// 此时，i_count=1，即只有本函数在使用（前面已经判断过为0的情况）
	// 如果硬链接数为0，则释放文件对应的数据块和inode
	if (!inode->i_nlinks) {
		truncate(inode);
		free_inode(inode);
		return;
	}
	// 如果inode做过修改
	if (inode->i_dirt) {
		write_inode(inode);	/* we can sleep - so do again */
		wait_on_inode(inode);  /* 睡眠了，所以需要重复判断 */
		goto repeat;
	}
	inode->i_count--;
	return;
}

// 从i节点表(inode_table）中获取一个空闲i节点项（i_count=0）。
// 从i节点表寻找引用计数 count 为0的i节点，并将其写盘后清零（如果脏），返回其指针。引用计数被置1。
// 如果找不到空闲的inode，则panic。
struct m_inode * get_empty_inode(void)
{
	struct m_inode * inode;
	static struct m_inode * last_inode = inode_table;  // inode表第一项
	int i;

	// 从内存i节点表中寻找空闲的inode
	do {
		inode = NULL;
		// 遍历inode表，查找空闲inode（满足：i_count、i_dirt、i_lock 均为0值）
		for (i = NR_INODE; i ; i--) {
			// 如果 last_inode 已经指向i节点表的最后1项之后，则让其重新指向i节点表开始处，以继续循环寻找空闲 i节点项
			if (++last_inode >= inode_table + NR_INODE)
				last_inode = inode_table;
			// 如果 last_inode 所指向的i节点的计数值为0，则说明可能找到空闲i节点项。
			if (!last_inode->i_count) {
				inode = last_inode;
				// 如果该i节点的已修改标志和锁定标志均为0，则我们可以使用该 i节点，于是退出for 循环
				if (!inode->i_dirt && !inode->i_lock)
					break;
			}
		}

		// 如果没有找到空闲 i节点 (inode= NULL)，则将 i节点表打印出来供调试使用，并停机。
		// 在Linux-0.99中已经改掉这个逻辑，变成不断重试。https://www.zhihu.com/question/442273405/answer/1708779674
		if (!inode) {
			for (i=0 ; i<NR_INODE ; i++)
				printk("%04x: %6d\t",inode_table[i].i_dev,
					inode_table[i].i_num);
			panic("No free inodes in mem");
		}
		wait_on_inode(inode);
		while (inode->i_dirt) {
			// i节点写盘
			write_inode(inode);
			wait_on_inode(inode);
		}
	} while (inode->i_count); // 如果i节点又被其他占用的话（i节点的计数值不为0了），则重新寻找空闲 i节点。
	// 将该i节点项内容清零，并置引用计数为1
	memset(inode,0,sizeof(*inode));
	inode->i_count = 1;
	return inode;
}

// 获取管道节点。
// 首先扫描i节点表，寻找一个空闲i节点项，然后取得一页空闲内存供管道使用。
// 然后将得到的i节点的引用计数置为2(读者和写者)，初始化管道头和尾，置i节点的管道类型表示。
// 返回为i节点指针，如果失败则返回 NULL。
struct m_inode * get_pipe_inode(void)
{
	struct m_inode * inode;

	if (!(inode = get_empty_inode()))
		return NULL;
	// pipe节点的 i_size 字段指向缓冲区
	if (!(inode->i_size=get_free_page())) {
		inode->i_count = 0;
		return NULL;
	}
	inode->i_count = 2;	/* sum of readers/writers */
	PIPE_HEAD(*inode) = PIPE_TAIL(*inode) = 0;  // 管道头尾指针设置为0
	inode->i_pipe = 1; // 管道使用标志
	return inode;
}

// 从内存inode表或设备中（根据指定的i节点号）取得一个i节点
// 从设备上读取指定节点号的i节点到内存i节点表中，并返回该i节点指针。
// 参数：dev - 设备号；nr - i节点号（inode结构从1号开始）。
// 首先在i节点表中搜寻，若找到指定节点号的i节点，则在经过一些判断处理后返回该i节点指针。
// 否则从设备 dev 上读取指定i节点号的 i节点信息放入i节点表中，并返回该i节点指针。
struct m_inode * iget(int dev,int nr)
{
	struct m_inode * inode, * empty;

	if (!dev)
		panic("iget with dev==0");
	// 获取一个空闲的i节点
	empty = get_empty_inode();
	inode = inode_table;
	// 遍历内存i节点表
	while (inode < NR_INODE+inode_table) {
		// 从内存i节点表中查找指定的inode
		if (inode->i_dev != dev || inode->i_num != nr) {
			inode++;
			continue;
		}
		// 从内存i节点表中找到了inode
		wait_on_inode(inode);
		// 等待唤醒之后，如果inode发生变化，则重新扫描内存inode表
		if (inode->i_dev != dev || inode->i_num != nr) {
			inode = inode_table;
			continue;
		}
		// 从内存i节点表中找到了一个解锁的inode
		inode->i_count++;
		// 如果该inode是文件系统的安装点(挂载点)
		if (inode->i_mount) {
			int i;

			// 查找安装在该inode的文件系统
			for (i = 0 ; i<NR_SUPER ; i++)
				if (super_block[i].s_imount==inode)
					break;
			// 如果未找到安装在该inode的文件系统
			if (i >= NR_SUPER) {
				printk("Mounted inode hasn't got sb\n");
				// 释放empty
				if (empty)
					iput(empty);
				return inode;
			}
			// 如果找到安装在该inode的文件系统，则返回被安装文件系统根节点
			iput(inode);
			dev = super_block[i].s_dev;
			nr = ROOT_INO;
			inode = inode_table;
			continue;
		}
		// 最终我们找到了相应的i节点。因此可以放弃本函数开始处临时申请的空闲i节点，返回找到的i节点指针。
		if (empty)
			iput(empty);
		return inode;
	}
	// 如果我们在i节点表中没有找到指定的i节点，则利用前面申请的空闲i节点 empty 在i 节点表中建立该i节点。
	// 并从相应设备上读取该i节点信息，返回该i节点指针。
	if (!empty)
		return (NULL);
	inode=empty;
	inode->i_dev = dev;
	inode->i_num = nr;
	// 从磁盘读取inode信息到内存inode中
	read_inode(inode);
	return inode;
}

// 从设备上读取含有指定i节点信息的i节点盘块，然后复制到指定的i节点结构中。
static void read_inode(struct m_inode * inode)
{
	struct super_block * sb;
	struct buffer_head * bh;
	int block;

	lock_inode(inode);
	if (!(sb=get_super(inode->i_dev)))
		panic("trying to read inode without dev");

	// inode所在的盘块号（盘块号是从0开始编号，0号盘块是引导块，1号盘块是超级块）
	// 虽然 i 节点号从 0 开始编号，但第 1 个 0 号 i 节点不用，并且磁盘上也不保存对应的 0 号 i 节点结构。
	// 因此存放 i 节点的盘块的第 1 块上保存的是 i 节点号是是 1--16 的 i 节点结构而不是 0--15 的。所以需要减1
	block = 2 + sb->s_imap_blocks + sb->s_zmap_blocks +
		(inode->i_num-1)/INODES_PER_BLOCK;  
	if (!(bh=bread(inode->i_dev,block)))
		panic("unable to read i-node block");
	*(struct d_inode *)inode =
		((struct d_inode *)bh->b_data)
			[(inode->i_num-1)%INODES_PER_BLOCK];
	brelse(bh);
	unlock_inode(inode);
}

// 将i节点信息写入缓冲区中。
// 该函数把参数指定的i节点写入缓冲区相应的缓冲块中，待缓冲区刷新时会写入盘中。
static void write_inode(struct m_inode * inode)
{
	struct super_block * sb;
	struct buffer_head * bh;
	int block;

	lock_inode(inode);
	if (!inode->i_dirt || !inode->i_dev) {
		unlock_inode(inode);
		return;
	}
	if (!(sb=get_super(inode->i_dev)))
		panic("trying to write inode without device");
	block = 2 + sb->s_imap_blocks + sb->s_zmap_blocks +
		(inode->i_num-1)/INODES_PER_BLOCK;
	if (!(bh=bread(inode->i_dev,block)))
		panic("unable to read i-node block");
	((struct d_inode *)bh->b_data)
		[(inode->i_num-1)%INODES_PER_BLOCK] =
			*(struct d_inode *)inode;
	// i节点写入缓冲区后，置位缓冲区的脏标志，同时inode的脏标识可复位
	bh->b_dirt=1;
	inode->i_dirt=0;
	brelse(bh);
	unlock_inode(inode);
}
